﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using Sino.CapacityCloud.Infrastructure.Utilities;
using System.Net;
using Newtonsoft.Json;
using System.Text;
using Sino.CapacityCloud.Infrastructure.Features;

namespace Sino.CapacityCloud.Infrastructure.Midwares
{

    public class ApiCompatibilityVerificationMiddleware
    {

        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public Func<OsCode, string, bool> Strategy { get; }

        public ApiCompatibilityVerificationMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, Func<OsCode, string, bool> strategy)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<ApiCompatibilityVerificationMiddleware>();
            this.Strategy = strategy;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments("/api"))
            {
                try
                {
                    // TODO: By default, if the client doesn't provide the ApiVersion and OsCode in the request header, we assume the request is valid.
                    OsCode oscode = OsCode.Unknown;
                    string version = "";
                    if (context.Request.Headers.ContainsKey("ApiVersion"))
                    {
                        version = context.Request.Headers["ApiVersion"].ToString();
                        if (!Enum.TryParse<OsCode>(context.Request.Headers?["OsCode"].ToString() ?? "", out oscode))
                        {
                            oscode = OsCode.Unknown;
                        }
                    }
                    var osName = Enum.GetName(typeof(OsCode), oscode);
                    this._logger.LogInformation($"Handling request from platform {osName} and app version {version}.");
                    if (!this.Strategy(oscode, version))
                    {
                        context.Response.OnStarting(async () =>
                        {
                            context.Response.Clear();
                            context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                            await context.Response.WriteAsync(JsonConvert.SerializeObject(new
                            {
                                Message = $"Handling request from incompatible platform {osName} and app version {version}."
                            }));
                        });
                    }
                    context.Features[typeof(IApiCompatibilityFeature)] = new ApiCompatibilityFeature()
                    {
                        ApiVersion = version,
                        ClientOperatingSystem = osName
                    };
                }
                catch (Exception ex)
                {
                    this._logger.LogError($"Failed to extract api compatibility info from the request because: {ex.Message}.", ex);
                }
            }
            await _next.Invoke(context);
        }

    }

    public static class ApiCompatibilityVerificationMiddlewareExtensions
    {

        public static IApplicationBuilder UseRequestApiCompatibilityVerificationMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseRequestApiCompatibilityVerificationMiddleware((oscode, version) => true);
        }

        public static IApplicationBuilder UseRequestApiCompatibilityVerificationMiddleware(this IApplicationBuilder builder, Version currentVersion)
        {
            return builder.UseRequestApiCompatibilityVerificationMiddleware((oscode, version) => new Version(version) >= currentVersion);
        }

        public static IApplicationBuilder UseRequestApiCompatibilityVerificationMiddleware(this IApplicationBuilder builder, Func<OsCode, string, bool> strategy)
        {
            return builder.UseMiddleware<ApiCompatibilityVerificationMiddleware>(strategy);
        }

    }

}